using System;
using System.Collections.Generic;
using Me.Prettyprint.Cassandra.Model;
using Me.Prettyprint.Cassandra.Model.Thrift;
using Me.Prettyprint.Cassandra.Serializers;
using Me.Prettyprint.Cassandra.Service;
using Me.Prettyprint.Cassandra.Utils;
using Me.Prettyprint.Hector.Api;
using Me.Prettyprint.Hector.Api.Beans;
using Me.Prettyprint.Hector.Api.Factory;
using Me.Prettyprint.Hector.Api.Mutation;
using Org.Apache.Cassandra.Thrift;
using Thrift;

namespace Me.Prettyprint.Cassandra.Service.Template
{
    /// <summary>
    /// Note: due to differences in generics, SuperCfUpdater includes generic V, since
    /// HColumn<N,V> requires it. In Java, the V isn't necessary.
    /// 
    /// This provides an interface of updating a specified row, most likely with the
    /// contents of an object. This would likely by implemented as an anonymous inner
    /// class with access to a final object in scope. It would update the given row
    /// with the object's data.
    /// 
    /// This is currently implemented as an abstract base class instead of an
    /// interface. This could change in the future. Being an abstract base class
    /// allows CassandraTemplate to initialize this instance through package scope
    /// field access. This means that implementation of update() simply makes
    /// consecutive calls to various set****() methods which already have the
    /// contextual information they need to update the correct row.
    /// 
    /// The downside of this approach is that the updater is essentially stateful and
    /// cannot be used concurrently. The alternative is to pass an object in to
    /// update() as a parameter with the setter methods, leaving the updater to be
    /// stateless.
    /// </summary>
    /// <typeparam name="K"></typeparam>
    /// <typeparam name="SN"></typeparam>
    /// <typeparam name="N"></typeparam>
    /// <typeparam name="V"></typeparam>
	public class SuperCfUpdater<K,SN,N> : AbstractTemplateUpdater<K, N>
	{
        /// <summary>
        /// Note: the constructor is different than Java, since we can't cast
        /// SuperCfTemplate<K,SN,N> to AbstractColumnFamilyTemplate<K, N>.
        /// Looking at the class, it only uses template field defined
        /// by the class, and never calls super.template.
        /// </summary>
        /// <param name="sTemplate"></param>
        /// <param name="columnFactory"></param>
        public SuperCfUpdater(SuperCfTemplate<K, SN, N> sTemplate, ColumnFactory columnFactory)
            : base(columnFactory, sTemplate.CreateMutator())
        {
            SuperTemplate = sTemplate;
            Clock = sTemplate.Clock;
            SubColumns = new List<object>();
        }
        protected SuperCfTemplate<K,SN,N> SuperTemplate
        {
            get;set;
        }
        protected List<SN> SuperColumnNames
        {
            get;
            set;
        }
        protected int SuperColPos
        {
            get;set;
        }
        /// <summary>
        /// We had to change this from HColumn<N,V> to HColumn<N,object> so that
        /// C# would allow it. Since everything in C# is an object, it's basically
        /// equivalent to <N,?>.
        /// </summary>
        protected List<object> SubColumns
        {
            get;
            set;
        }
        public new SuperCfUpdater<K, SN, N> AddKey(K key)
        {

            if (Keys != null && Keys.Count > 0)
            {
                UpdateInternal();
            }
            base.AddKey(key);
            SuperColumnNames = new List<SN>();
            SuperColPos = 0;
            return this;
        }
        public SuperCfUpdater<K, SN, N> AddSuperColumn(SN sColumnName)
        {
            if (SuperColumnNames.Count > 0)
            {
                UpdateInternal();
                SuperColPos++;
            }
            if (SubColumns.Count == 0)
            {
                SubColumns = new List<object>();
            }
            SuperColumnNames.Add(sColumnName);
            return this;
        }
        public SN GetCurrentSuperColumn()
        {
            return SuperColumnNames[SuperColPos];
        }
        public void UpdateInternal()
        {
            // Due to the fact that C# doesn't allow wild cards, we have to use a big
            // if/else if block to convert the SubColumns to a concrete generic list
            // with the correct value type for the HColumn
            // TODO finish me
            if (SubColumns.Count != 0)
            {
                foreach (object obj in SubColumns)
                {
                    if (obj is HColumn<N, float>)
                    {
                        List<HColumn<N,float>> sc = new List<HColumn<N, float>>();
                        sc.Add((HColumn<N, float>)obj);
                        HSuperColumnImpl<SN, N, float> column = new HSuperColumnImpl<SN, N, float>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, FloatSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, double>)
                    {
                        List<HColumn<N, double>> sc = new List<HColumn<N, double>>();
                        sc.Add((HColumn<N, double>)obj);
                        HSuperColumnImpl<SN, N, double> column = new HSuperColumnImpl<SN, N, double>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, DoubleSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, long>)
                    {
                        List<HColumn<N, long>> sc = new List<HColumn<N, long>>();
                        sc.Add((HColumn<N, long>)obj);
                        HSuperColumnImpl<SN, N, long> column = new HSuperColumnImpl<SN, N, long>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, LongSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, int>)
                    {
                        List<HColumn<N, int>> sc = new List<HColumn<N, int>>();
                        sc.Add((HColumn<N, int>)obj);
                        HSuperColumnImpl<SN, N, int> column = new HSuperColumnImpl<SN, N, int>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, IntegerSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, bool>)
                    {
                        List<HColumn<N, bool>> sc = new List<HColumn<N, bool>>();
                        sc.Add((HColumn<N, bool>)obj);
                        HSuperColumnImpl<SN, N, bool> column = new HSuperColumnImpl<SN, N, bool>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, BooleanSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, string>)
                    {
                        List<HColumn<N, string>> sc = new List<HColumn<N, string>>();
                        sc.Add((HColumn<N, string>)obj);
                        HSuperColumnImpl<SN, N, string> column = new HSuperColumnImpl<SN, N, string>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, StringSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, DateTime>)
                    {
                        List<HColumn<N, DateTime>> sc = new List<HColumn<N, DateTime>>();
                        sc.Add((HColumn<N, DateTime>)obj);
                        HSuperColumnImpl<SN, N, DateTime> column = new HSuperColumnImpl<SN, N, DateTime>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, DateSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, Guid>)
                    {
                        List<HColumn<N, Guid>> sc = new List<HColumn<N, Guid>>();
                        sc.Add((HColumn<N, Guid>)obj);
                        HSuperColumnImpl<SN, N, Guid> column = new HSuperColumnImpl<SN, N, Guid>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, UUIDSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, double>)
                    {
                        List<HColumn<N, byte[]>> sc = new List<HColumn<N, byte[]>>();
                        sc.Add((HColumn<N, byte[]>)obj);
                        HSuperColumnImpl<SN, N, byte[]> column = new HSuperColumnImpl<SN, N, byte[]>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, BytesArraySerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                    else if (obj is HColumn<N, ByteBuffer>)
                    {
                        List<HColumn<N, ByteBuffer>> sc = new List<HColumn<N, ByteBuffer>>();
                        sc.Add((HColumn<N, ByteBuffer>)obj);
                        HSuperColumnImpl<SN, N, ByteBuffer> column = new HSuperColumnImpl<SN, N, ByteBuffer>(GetCurrentSuperColumn(), sc,
                            0, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer, ByteBufferSerializer.Instance);
                        CurrentMutator.AddInsertion(GetCurrentKey(), SuperTemplate.ColumnFamily, column);
                    }
                }
            }
        }
        public void deleteSuperColumn()
        {
            CurrentMutator.AddSuperDelete(GetCurrentKey(), SuperTemplate.ColumnFamily,
                GetCurrentSuperColumn(), SuperTemplate.TopSerializer);
        }
        public void DeleteSubColumn(N columnName)
        {
            CurrentMutator.AddSubDelete(GetCurrentKey(), SuperTemplate.ColumnFamily, 
                GetCurrentSuperColumn(), columnName, SuperTemplate.TopSerializer, SuperTemplate.SubSerializer);
        }
        public void SetString(N subColumnName, string value)
        {
            AddToSubColumns(subColumnName, value, StringSerializer.Instance, Ttl); 
        }
        public void SetUUID(N subColumnName, Guid value)
        {
            AddToSubColumns(subColumnName, value, UUIDSerializer.Instance, Ttl);
        }
        public void SetLong(N subColumnName, long value)
        {
            AddToSubColumns(subColumnName, value, LongSerializer.Instance, Ttl);
        }
        public void SetInteger(N subColumnName, int value)
        {
            AddToSubColumns(subColumnName, value, IntegerSerializer.Instance, Ttl);
        }
        public void SetBoolean(N subColumnName, bool value)
        {
            AddToSubColumns(subColumnName, value, BooleanSerializer.Instance, Ttl);
        }
        public void setByteArray(N subColumnName, byte[] value)
        {
            AddToSubColumns(subColumnName, value, BytesArraySerializer.Instance, Ttl);
        }
        public void setByteBuffer(N subColumnName, ByteBuffer value)
        {
            AddToSubColumns(subColumnName, value, ByteBufferSerializer.Instance, Ttl);
        }
        public void SetDate(N subColumnName, DateTime value)
        {
            AddToSubColumns(subColumnName, value, DateSerializer.Instance, Ttl);
        }
        public void setFloat(N subColumnName, float value)
        {
            HColumn<N, float> col = ColumnFactory.CreateColumn(subColumnName, value, Clock,
                SuperTemplate.SubSerializer, FloatSerializer.Instance);
            SubColumns.Add((HColumn<N,float>)col);
        }
        public void SetDouble(N subColumnName, double value)
        {
            AddToSubColumns(subColumnName, value, DoubleSerializer.Instance, Ttl);
        }
        public void SetValue<V>(N subColumnName, V value, Serializer<V> serializer)
        {
            AddToSubColumns(subColumnName, value, serializer, Ttl);
        }
        private void AddToSubColumns<V>(N subColumnName, V value, Serializer<V> valueSerializer, int ttl)
        {
            HColumn<N,V> col = ColumnFactory.CreateColumn(subColumnName, value, SuperTemplate.SubSerializer, valueSerializer); 
            if(ttl > DEF_TTL)
            {
                col.SetTtl(Ttl); 
            }
            SubColumns.Add(col);
        }
	}
}